library(ggplot2)
library(MSnbase)
library(biobroom)
library(camprotR)
library(Proteomics.analysis.data)
library(dplyr)
library(tidyr)
library(here)
library(DEqMS)
library(limma)
library(broom)
Input data.
We’ll start by reading in the data to a data.frame, which is a
generic data.structure, suitable for any data table. This is the
required input for CamprotR::parse_features and allows us
to perform manual filtering with e.g dplyr easily
pep_data <- read.delim(here("2024_02_13_Peptide_data.txt"))
filter crap proteins
crap_fasta_inf <-here('2023_02_CCP_cRAP.fasta.gz')
# Load the cRAP FASTA used for the PD search
crap_fasta <- Biostrings::fasta.index(crap_fasta_inf, seqtype = "AA")
# Extract the UniProt accessions associated with each cRAP protein
crap_accessions <- crap_fasta %>%
pull(desc) %>%
stringr::str_extract_all(pattern="(?<=\\|).*?(?=\\|)") %>%
unlist()
pep_data_flt <- camprotR::parse_features(
pep_data,
level = 'peptide',
crap_proteins = crap_accessions,
unique_master = FALSE
)
Parsing features...
5182 features found from 1191 master proteins => Input
250 cRAP proteins supplied
357 proteins identified as 'cRAP associated'
4869 features found from 1164 master proteins => cRAP features removed
4823 features found from 1136 master proteins => associated cRAP features removed
#level = 'peptide': This parameter specifies that you're interested in parsing peptide-level features from pep_data.
#crap_proteins = crap_accessions: This parameter seems to specify a vector (crap_accessions) containing protein accessions that are considered contaminants or irrelevant for your analysis. The function likely filters out peptides associated with these proteins.
Filter for unique master proteins
pep_data_flt <- pep_data_flt %>% filter(!grepl(';', Master.Protein.Accessions))
camprotR:::message_parse(pep_data_flt, 'Master.Protein.Accessions', "features with non-unique master proteins removed")
4581 features found from 1056 master proteins => features with non-unique master proteins removed
sample_data <-read.csv("Sample_data.csv")
# Displaying the table in a nicer format
knitr::kable(sample_data,
align = "cccc",
format = "html",
table.attr = "style='width:30%;'")
|
File
|
Sample
|
Condition
|
Replicate
|
|
1
|
NCL_VEH_1
|
NCL
|
1
|
|
2
|
NCL_LNA_1
|
NCL
|
1
|
|
3
|
NCL_ION_1
|
NCL
|
1
|
|
4
|
CL_VEH_1
|
CL
|
1
|
|
5
|
CL_LNA_1
|
CL
|
1
|
|
6
|
CL_ION_1
|
CL
|
1
|
|
7
|
NCL_VEH_2
|
NCL
|
2
|
|
8
|
NCL_LNA_2
|
NCL
|
2
|
|
9
|
NCL_ION_2
|
NCL
|
2
|
|
10
|
CL_VEH_2
|
CL
|
2
|
|
11
|
CL_LNA_2
|
CL
|
2
|
|
12
|
CL_ION_2
|
CL
|
2
|
|
13
|
NCL_VEH_3
|
NCL
|
3
|
|
14
|
NCL_LNA_3
|
NCL
|
3
|
|
15
|
NCL_ION_3
|
NCL
|
3
|
|
16
|
CL_VEH_3
|
CL
|
3
|
|
17
|
CL_LNA_3
|
CL
|
3
|
|
18
|
CL_ION_3
|
CL
|
3
|
Convert to an MsnSet
A MsnSet is a proteomics-specific data structure,
with functions available for standard proteomics workflows. It’s since
been deprecated and replaced by Qfeatures, which allows
multiple levels of quantification to be stored in the same object, with
details about how the features in each level are linked. Nonetheless, a
MsnSet is still a sensible data structure to use for
proteomics data. Many camprotR functions have been written
explicitly to work with MsnSets, for example
plot_quant in the code block after this.
=
#select columns with the word "abundance" in.
exprs_data <- pep_data_flt %>%
select(matches("Abundance..F.*")) %>%
as.matrix()
# check the order of the samples. They appear to be ordered as rep1-3 for each condition.
# If the replicate detail is important, e.g performed on separate days, it would be
# prudent to check with Sandip if they were indeed all run in order rep1-3.
# He should have shared an InputFiles.txt file which will specify the full sample name for each run,
# which would hopefully detail the replicate number
print(colnames(exprs_data))
[1] "Abundance..F6..Sample..CL_ION"
[2] "Abundance..F12..Sample..CL_ION"
[3] "Abundance..F18..Sample..CL_ION"
[4] "Abundance..F5..Sample..CL_LNA"
[5] "Abundance..F11..Sample..CL_LNA"
[6] "Abundance..F17..Sample..CL_LNA"
[7] "Abundance..F4..Sample..CL_VEH"
[8] "Abundance..F10..Sample..CL_VEH"
[9] "Abundance..F16..Sample..CL_VEH"
[10] "Abundance..F3..Sample..NCL_ION"
[11] "Abundance..F9..Sample..NCL_ION"
[12] "Abundance..F15..Sample..NCL_ION"
[13] "Abundance..F2..Sample..NCL_LNA"
[14] "Abundance..F8..Sample..NCL_LNA"
[15] "Abundance..F14..Sample..NCL_LNA"
[16] "Abundance..F1..Sample..NCL_VEH"
[17] "Abundance..F7..Sample..NCL_VEH"
[18] "Abundance..F13..Sample..NCL_VEH"
# Remove the unwanted part of the column name
colnames(exprs_data) <- gsub('Abundance...*..Sample..', '', colnames(exprs_data))
# Add the replicate number
colnames(exprs_data) <- paste(colnames(exprs_data), rep(c(1,2,3), 6), sep='_')
# Create data.frame with sample metadata (pData)
#takes sample data and selects every column without "file" in the name, then creates a tibble with the rownames as the column "sample"
pheno_data <- sample_data %>%
select(-File) %>%
tibble::column_to_rownames(var = "Sample")
# Reorder the phenotype data to be in the same order as the exprs matrix
# Example dataframe
# Define the desired order of columns
desired_order <- c("CL_ION_1","CL_ION_2","CL_ION_3", "CL_LNA_1","CL_LNA_2","CL_LNA_3", "CL_VEH_1","CL_VEH_2","CL_VEH_3","NCL_ION_1","NCL_ION_2","NCL_ION_3", "NCL_LNA_1","NCL_LNA_2","NCL_LNA_3", "NCL_VEH_1","NCL_VEH_2","NCL_VEH_3")
# Subset the dataframe with the desired column order
pheno_data <- pheno_data[desired_order,]
# Create data.frame with peptide metadata (fData)
# select all columns except those with abundance in the name.
feature_data <- pep_data_flt %>%
# This retains all columns except the abundance columns. We don't really need most columns
#select(-matches("Abundance"))
# This just retains the useful columns
select(Annotated.Sequence, Modifications, Protein.Accessions, Master.Protein.Accessions)
# Create MSnSet
pep <- MSnbase::MSnSet(exprs = exprs_data,
fData = feature_data,
pData = pheno_data)
Let’s QC the peptides
pep %>%
log(base = 2) %>%
camprotR::plot_quant(method = 'box')

let’s look at some cool peptide intensities
pep %>%
log(base = 2) %>%
camprotR::plot_quant(method = 'density') +
scale_colour_manual(values=rep(get_cat_palette(6), each=3))

p <- MSnbase::plotNA(pep, pNA = 0) +
camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 10) +
labs(x = 'Peptide index')

naniar::gg_miss_upset is a generic function, not one specifically
for proteomics data. As such, it was designed to take a data.frame()
with just numeric columns. Hence we extract the exprs matrix from the
MsnSet and convert to a data.frame
missing_data <- pep %>%
exprs() %>%
data.frame()
naniar::gg_miss_upset(missing_data,
sets = paste0(colnames(pep), '_NA'),
keep.order = TRUE,
nsets = 10)


I Don’t think we need to do any normalising to peptide intensities
because we didn’t inject the same amount of peptide, it’s a
pulldown.
Let’s do some summarising to protein-level abundances
Relaxing max NA to 2/3 and reducing min peptides per protein to 2
(from 3) doubles the retained peptides to ~2000.
pep_restricted <- pep %>%
# Maximum 2/3 missing values
MSnbase::filterNA(pNA = 2/3) %>% # With 18 sample, allowing missing in 12 seems reasonable to me.
# At least two peptides per protein
camprotR::restrict_features_per_protein(min_features = 2, plot = FALSE) %>%
# Repeat the filtering since restrict_features_per_protein will replace some values with NA
MSnbase::filterNA(pNA = 2/3) %>%
camprotR::restrict_features_per_protein(min_features = 2, plot = FALSE)
p <- MSnbase::plotNA(pep_restricted, pNA = 0) +
camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 15) +
labs(x = 'Peptide index')

Warning message re missing values can be safely ignored here.
MSnbase::combineFeatures doesn’t do any sanity checking
that NA values are being appropriately handled and gives this warning so
the onus is on the user to know what they are doing.
method='robust' can handle NA values appropriately. Any
proteins with NA value following this will be because the model could
not estimate protein abundance from the peptide abundance. For example,
in the following table, where X = peptide quantified, the protein cannot
be quantified in sample A, since the peptides quantified in sample A are
not quantified in any other sample. This is why we need to pre-filter
the peptides to only retain the most informative ones
| pep1 |
X |
|
|
|
| pep2 |
|
X |
X |
X |
| pep3 |
X |
|
|
|
| pep4 |
|
X |
X |
X |
prot_robust <- pep_restricted %>%
log(base=2) %>%
MSnbase::combineFeatures(
# group the peptides by their master protein id
groupBy = fData(pep_restricted)$Master.Protein.Accessions,
method = 'robust',
maxit = 1000 # Ensures convergence for MASS::rlm
)
Your data contains missing values. Please read the relevant
section in the combineFeatures manual page for details on the
effects of missing values on data aggregation.
p <- MSnbase::plotNA(prot_robust, pNA = 0) +
camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 15)

naniar::gg_miss_upset(data.frame(exprs(prot_robust)),
sets = paste0(colnames(prot_robust), '_NA'),
keep.order = TRUE,
nsets = 10)


saveRDS(prot_robust, 'lfq_prot_robust.rds')
Shall we do some statistical testing?
Load in the QC’d LFQ data from the rds from before
lfq_protein <- readRDS('lfq_prot_robust.rds')
We need to convert the data structure from MsnSet to a data.frame
to make it easy to filter to proteins with at least two replicate with
quantification values for each set of samples. It would be possible to
do this with the MsnSet, though I think it would prove pretty
painful
Note, code below is an example for how to test CL vs NC for the 6 LNA
samples. If you are performing many different comparisons, it may make
sense to create a function(s) for some of this to avoid repeating the
same code and having lots of similarly named objects around. I’ve
indicated the most obvious parts where functions could be written. - -
adding the counts to the limma object
———————Test LNA CL vs NCL data————————–
lfq_protein_tidy_lna <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
subset(sample=="NCL_LNA_1"| sample=="NCL_LNA_2" | sample=="NCL_LNA_3" | sample=="CL_LNA_1"| sample=="CL_LNA_2" | sample=="CL_LNA_3") %>%
filter(is.finite(value)) %>%
group_by(protein, Condition) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Condition))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_lna <- lfq_protein_tidy_lna %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_lna)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))
limma_design_lna <- model.matrix(formula(~treatment))
limma_fit_lna <- lmFit(filtered_exprs_lna, limma_design_lna)
limma_fit_lna <- eBayes(limma_fit_lna, trend=TRUE)
limma::plotSA(limma_fit_lna)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_lna <- filtered_exprs_lna %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_lna <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_lna, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_lna$count <- min_pep_count_lna$min_pep_count
efit_deqms_lna <- suppressWarnings(spectraCounteBayes(limma_fit_lna))
VarianceBoxplot(efit_deqms_lna, n = 30, xlab = "Peptides")

deqms_results_lna <- outputResult(efit_deqms_lna, coef_col=2)
hist(deqms_results_lna$P.Value)

hist(deqms_results_lna$sca.P.Value)

table(deqms_results_lna$sca.adj.pva<0.1)
FALSE
174
deqms_results_lna %>% filter(logFC>0) %>% head()
deqms_results_lna %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
labs(x = 'LNA CL/NCL', y = '-log10(p-value)')

———————Test ION CL vs NCL data————————–
lfq_protein_tidy_ion <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
subset(sample=="NCL_ION_1"| sample=="NCL_ION_2" | sample=="NCL_ION_3" | sample=="CL_ION_1"| sample=="CL_ION_2" | sample=="CL_ION_3") %>%
# TS: would be best to have a column in the pData which describes the VEH/LNA/ION variable, so you can just filter using that column
subset(sample %in% c("NCL_ION_1", "NCL_ION_2", "NCL_ION_3", "CL_ION_1", "CL_ION_2", "CL_ION_3")) %>%
filter(is.finite(value)) %>%
group_by(protein, Condition) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Condition))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion <- lfq_protein_tidy_ion %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_ion)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))
limma_design_ion <- model.matrix(formula(~treatment))
limma_fit_ion <- lmFit(filtered_exprs_ion, limma_design_ion)
limma_fit_ion <- eBayes(limma_fit_ion, trend=TRUE)
limma::plotSA(limma_fit_ion)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion <- filtered_exprs_ion %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_ion <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_ion, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_ion$count <- min_pep_count_ion$min_pep_count
efit_deqms_ion <- suppressWarnings(spectraCounteBayes(limma_fit_ion))
VarianceBoxplot(efit_deqms_ion, n = 30, xlab = "Peptides")

deqms_results_ion <- outputResult(efit_deqms_ion, coef_col=2)
hist(deqms_results_ion$P.Value)

hist(deqms_results_ion$sca.P.Value)

table(deqms_results_ion$sca.adj.pva<0.1)
FALSE
110
deqms_results_ion %>% filter(logFC>0) %>% head()
deqms_results_ion %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
labs(x = 'ION CL/NCL', y = '-log10(p-value)')

———————Test VEH CL vs NCL data————————–
lfq_protein_tidy_veh <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
subset(sample=="NCL_VEH_1"| sample=="NCL_VEH_2" | sample=="NCL_VEH_3" | sample=="CL_VEH_1"| sample=="CL_VEH_2" | sample=="CL_VEH_3") %>%
filter(is.finite(value)) %>%
group_by(protein, Condition) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Condition))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_veh <- lfq_protein_tidy_veh %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_veh)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))
limma_design_veh <- model.matrix(formula(~treatment))
limma_fit_veh <- lmFit(filtered_exprs_veh, limma_design_veh)
limma_fit_veh <- eBayes(limma_fit_veh, trend=TRUE)
limma::plotSA(limma_fit_veh)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_veh <- filtered_exprs_veh %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_veh <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_veh, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_veh$count <- min_pep_count_veh$min_pep_count
efit_deqms_veh <- suppressWarnings(spectraCounteBayes(limma_fit_veh))
VarianceBoxplot(efit_deqms_veh, n = 30, xlab = "Peptides")

deqms_results_veh <- outputResult(efit_deqms_veh, coef_col=2)
hist(deqms_results_veh$P.Value)

hist(deqms_results_veh$sca.P.Value)

table(deqms_results_veh$sca.adj.pva<0.1)
FALSE
112
deqms_results_veh %>% filter(logFC>0) %>% head()
deqms_results_veh %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
labs(x = 'VEH_CL/NCL', y = '-log10(p-value)')

———————Test ION CL vs LNA CL data————————–
Let’s visualise the abundance distributions of the samples in
question.
plot_quant(lfq_protein[,grepl('^CL_(ION|LNA)_\\d', colnames(lfq_protein))], method='density') +
scale_colour_manual(values=rep(get_cat_palette(2), each=3))

plot_quant(lfq_protein[,grepl('^CL_(ION|LNA)_\\d', colnames(lfq_protein))], method='box')

lfq_protein_tidy_ION_LNA <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
filter(grepl('^CL_(ION|LNA)_\\d', sample)) %>%
separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
filter(is.finite(value)) %>%
group_by(protein) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Treatment))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion_lna_cl <- lfq_protein_tidy_ION_LNA %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
library(tibble)
pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_ion_lna_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE)
treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('ION', 'LNA'))
replicate <- pheno_data2$Replicate
limma_design_ion_lna_cl <- model.matrix(formula(~replicate+treatment2))
limma_fit_ion_lna_cl <- lmFit(filtered_exprs_ion_lna_cl, limma_design_ion_lna_cl)
Warning: Partial NA coefficients for 35 probe(s)
limma_fit_ion_lna_cl <- eBayes(limma_fit_ion_lna_cl, trend=TRUE)
limma::plotSA(limma_fit_ion_lna_cl)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion_lna_cl <- filtered_exprs_ion_lna_cl %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_ion_lna_cl <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_ion_lna_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_ion_lna_cl$count <- min_pep_count_ion_lna_cl$min_pep_count
efit_deqms_ion_lna_cl <- suppressWarnings(spectraCounteBayes(limma_fit_ion_lna_cl))
VarianceBoxplot(efit_deqms_ion_lna_cl, n = 30, xlab = "Peptides")

head(coefficients(efit_deqms_ion_lna_cl))
(Intercept) replicate treatment2LNA
E9PRG8 18.518541 -0.2047557 -0.6418709
O00567 17.244731 0.6021092 -2.1276393
O00763 14.560631 0.7504844 0.4756514
O14818 14.621599 0.6324806 1.6847410
O60832 18.193577 0.5491689 -1.4823259
O75083 7.997309 5.4577333 -4.2756229
deqms_results_ion_lna_cl <- outputResult(efit_deqms_ion_lna_cl, coef_col=3)
hist(deqms_results_ion_lna_cl$P.Value)

hist(deqms_results_ion_lna_cl$sca.P.Value)

table(deqms_results_ion_lna_cl$sca.adj.pva<0.1)
FALSE
194
deqms_results_ion_lna_cl %>% filter(logFC>0) %>% head()
deqms_results_veh %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'ION vs LNA Sig.') +
labs(x = 'IonCL vs LNACL (Log2)', y = '-log10(p-value)')

———————LNA v VEH————————–
lfq_protein_tidy_LNA_VEH <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
filter(grepl('^CL_(LNA|VEH)_\\d', sample)) %>%
separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
filter(is.finite(value)) %>%
group_by(protein) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Treatment))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_lna_veh_cl <- lfq_protein_tidy_LNA_VEH %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
library(tibble)
pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_lna_veh_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE)
treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('LNA', 'VEH'))
replicate <- pheno_data2$Replicate
limma_design_lna_veh_cl <- model.matrix(formula(~replicate+treatment2))
limma_fit_lna_veh_cl <- lmFit(filtered_exprs_lna_veh_cl, limma_design_lna_veh_cl)
Warning: Partial NA coefficients for 8 probe(s)
limma_fit_lna_veh_cl <- eBayes(limma_fit_lna_veh_cl, trend=TRUE)
limma::plotSA(limma_fit_lna_veh_cl)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_lna_veh_cl <- filtered_exprs_lna_veh_cl %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_lna_veh_cl <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_lna_veh_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_lna_veh_cl$count <- min_pep_count_lna_veh_cl$min_pep_count
efit_deqms_lna_veh_cl <- suppressWarnings(spectraCounteBayes(limma_fit_lna_veh_cl))
VarianceBoxplot(efit_deqms_lna_veh_cl, n = 30, xlab = "Peptides")

head(coefficients(efit_deqms_lna_veh_cl))
(Intercept) replicate treatment2VEH
E9PRG8 19.19580 -0.8643230 2.7197521
O00567 16.69525 -0.1869708 0.8729443
O00763 16.05610 0.2405753 1.0391871
O60832 18.11754 -0.1539763 -1.1314673
O75223 18.12962 -0.9791181 0.9090755
O75683 18.45775 -1.2348835 1.7131505
deqms_results_lna_veh_cl <- outputResult(efit_deqms_lna_veh_cl, coef_col=3)
hist(deqms_results_lna_veh_cl$P.Value)

hist(deqms_results_lna_veh_cl$sca.P.Value)

table(deqms_results_lna_veh_cl$sca.adj.pva<0.1)
FALSE
229
deqms_results_lna_veh_cl %>% filter(logFC>0) %>% head()
deqms_results_lna_veh_cl %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'LNA vs VEH Sig.') +
labs(x = 'LNACL vs VEHCL (Log2)', y = '-log10(p-value)')

———————ION_CLv VEH_CL————————–
Let’s visualise the abundance distributions of the samples in
question.
plot_quant(lfq_protein[,grepl('^CL_(ION|VEH)_\\d', colnames(lfq_protein))], method='density') +
scale_colour_manual(values=rep(get_cat_palette(2), each=3))

plot_quant(lfq_protein[,grepl('^CL_(ION|VEH)_\\d', colnames(lfq_protein))], method='box')

lfq_protein_tidy_ION_VEH <- lfq_protein %>%
# make the MSnSet into 'tidy' format for further testing
biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
filter(grepl('^CL_(ION|VEH)_\\d', sample)) %>%
separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
filter(is.finite(value)) %>%
group_by(protein) %>%
filter(n()>=2) %>%
group_by(protein) %>%
filter(length(unique(Treatment))==2) %>%# n() is the length of the group
ungroup()
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion_veh_cl <- lfq_protein_tidy_ION_VEH %>%
pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
tibble::column_to_rownames('protein') %>%
as.matrix()
# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
library(tibble)
pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_ion_veh_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE)
treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('ION', 'VEH'))
replicate <- pheno_data2$Replicate
limma_design_ion_veh_cl <- model.matrix(formula(~replicate+treatment2))
limma_fit_ion_veh_cl <- lmFit(filtered_exprs_ion_veh_cl, limma_design_ion_veh_cl)
Warning: Partial NA coefficients for 4 probe(s)
limma_fit_ion_veh_cl <- eBayes(limma_fit_ion_veh_cl, trend=TRUE)
limma::plotSA(limma_fit_ion_veh_cl)

# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion_veh_cl <- filtered_exprs_ion_veh_cl %>%
data.frame() %>%
tibble::rownames_to_column('Master.Protein.Accessions') %>%
pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified
min_pep_count_ion_veh_cl <- camprotR::count_features_per_protein(pep) %>%
merge(filtered_lfq_protein_long_ion_veh_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
group_by(Master.Protein.Accessions) %>%
summarise(min_pep_count = min(n))
###
# add the min peptide count
limma_fit_ion_veh_cl$count <- min_pep_count_ion_veh_cl$min_pep_count
efit_deqms_ion_veh_cl <- suppressWarnings(spectraCounteBayes(limma_fit_ion_veh_cl))
VarianceBoxplot(efit_deqms_ion_veh_cl, n = 30, xlab = "Peptides")

head(coefficients(efit_deqms_ion_veh_cl))
(Intercept) replicate treatment2VEH
E9PRG8 18.23591 -0.06343931 2.077881
O00567 16.37336 1.03779547 -1.254695
O00763 14.68158 0.69000834 1.514839
O14818 15.39226 0.24715110 1.593563
O60832 17.64914 0.82138624 -2.613793
O75083 15.44127 0.49509075 -1.474893
deqms_results_ion_veh_cl <- outputResult(efit_deqms_ion_veh_cl, coef_col=3)
hist(deqms_results_ion_veh_cl$P.Value)

hist(deqms_results_ion_veh_cl$sca.P.Value)

table(deqms_results_ion_veh_cl$sca.adj.pva<0.1)
FALSE TRUE
249 1
deqms_results_ion_veh_cl %>% filter(logFC>0) %>% head()
deqms_results_ion_veh_cl %>%
ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
geom_point() +
theme_camprot(border=FALSE, base_size=15) +
scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'ION vs VEH Sig.') +
labs(x = 'IONCL vs VEHCL (Log2)', y = '-log10(p-value)')

LS0tCnRpdGxlOiAiMjAyNF8wMl8yNl9BU09fQmlvdGluX3B1bGxkb3duX0RhdGFfcHJvY2Vzc2luZ19naXQiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoTVNuYmFzZSkKbGlicmFyeShiaW9icm9vbSkKbGlicmFyeShjYW1wcm90UikKbGlicmFyeShQcm90ZW9taWNzLmFuYWx5c2lzLmRhdGEpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoaGVyZSkKbGlicmFyeShERXFNUykKbGlicmFyeShsaW1tYSkKbGlicmFyeShicm9vbSkKCgpgYGAKCklucHV0IGRhdGEuCgoqV2UnbGwgc3RhcnQgYnkgcmVhZGluZyBpbiB0aGUgZGF0YSB0byBhIGRhdGEuZnJhbWUsIHdoaWNoIGlzIGEgZ2VuZXJpYyBkYXRhLnN0cnVjdHVyZSwgc3VpdGFibGUgZm9yIGFueSBkYXRhIHRhYmxlLiBUaGlzIGlzIHRoZSByZXF1aXJlZCBpbnB1dCBmb3IgYENhbXByb3RSOjpwYXJzZV9mZWF0dXJlc2AgYW5kIGFsbG93cyB1cyB0byBwZXJmb3JtIG1hbnVhbCBmaWx0ZXJpbmcgd2l0aCBlLmcgYGRwbHlyYCBlYXNpbHkqCgpgYGB7cn0KcGVwX2RhdGEgPC0gcmVhZC5kZWxpbShoZXJlKCIyMDI0XzAyXzEzX1BlcHRpZGVfZGF0YS50eHQiKSkKCmBgYAoKZmlsdGVyIGNyYXAgcHJvdGVpbnMKCmBgYHtyfQpjcmFwX2Zhc3RhX2luZiA8LWhlcmUoJzIwMjNfMDJfQ0NQX2NSQVAuZmFzdGEuZ3onKQoKIyBMb2FkIHRoZSBjUkFQIEZBU1RBIHVzZWQgZm9yIHRoZSBQRCBzZWFyY2gKY3JhcF9mYXN0YSA8LSBCaW9zdHJpbmdzOjpmYXN0YS5pbmRleChjcmFwX2Zhc3RhX2luZiwgc2VxdHlwZSA9ICJBQSIpCgojIEV4dHJhY3QgdGhlIFVuaVByb3QgYWNjZXNzaW9ucyBhc3NvY2lhdGVkIHdpdGggZWFjaCBjUkFQIHByb3RlaW4KY3JhcF9hY2Nlc3Npb25zIDwtIGNyYXBfZmFzdGEgJT4lCiAgcHVsbChkZXNjKSAlPiUKICBzdHJpbmdyOjpzdHJfZXh0cmFjdF9hbGwocGF0dGVybj0iKD88PVxcfCkuKj8oPz1cXHwpIikgJT4lCiAgdW5saXN0KCkKYGBgCgpgYGB7cn0KcGVwX2RhdGFfZmx0IDwtIGNhbXByb3RSOjpwYXJzZV9mZWF0dXJlcygKICBwZXBfZGF0YSwKICBsZXZlbCA9ICdwZXB0aWRlJywKICBjcmFwX3Byb3RlaW5zID0gY3JhcF9hY2Nlc3Npb25zLAogIHVuaXF1ZV9tYXN0ZXIgPSBGQUxTRQopCgoKI2xldmVsID0gJ3BlcHRpZGUnOiBUaGlzIHBhcmFtZXRlciBzcGVjaWZpZXMgdGhhdCB5b3UncmUgaW50ZXJlc3RlZCBpbiBwYXJzaW5nIHBlcHRpZGUtbGV2ZWwgZmVhdHVyZXMgZnJvbSBwZXBfZGF0YS4KI2NyYXBfcHJvdGVpbnMgPSBjcmFwX2FjY2Vzc2lvbnM6IFRoaXMgcGFyYW1ldGVyIHNlZW1zIHRvIHNwZWNpZnkgYSB2ZWN0b3IgKGNyYXBfYWNjZXNzaW9ucykgY29udGFpbmluZyBwcm90ZWluIGFjY2Vzc2lvbnMgdGhhdCBhcmUgY29uc2lkZXJlZCBjb250YW1pbmFudHMgb3IgaXJyZWxldmFudCBmb3IgeW91ciBhbmFseXNpcy4gVGhlIGZ1bmN0aW9uIGxpa2VseSBmaWx0ZXJzIG91dCBwZXB0aWRlcyBhc3NvY2lhdGVkIHdpdGggdGhlc2UgcHJvdGVpbnMuCmBgYAoKRmlsdGVyIGZvciB1bmlxdWUgbWFzdGVyIHByb3RlaW5zCgpgYGB7cn0KcGVwX2RhdGFfZmx0IDwtIHBlcF9kYXRhX2ZsdCAlPiUgZmlsdGVyKCFncmVwbCgnOycsIE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMpKQpjYW1wcm90Ujo6Om1lc3NhZ2VfcGFyc2UocGVwX2RhdGFfZmx0LCAnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICJmZWF0dXJlcyB3aXRoIG5vbi11bmlxdWUgbWFzdGVyIHByb3RlaW5zIHJlbW92ZWQiKQpgYGAKCmBgYHtyfQpzYW1wbGVfZGF0YSA8LXJlYWQuY3N2KCJTYW1wbGVfZGF0YS5jc3YiKQoKIyBEaXNwbGF5aW5nIHRoZSB0YWJsZSBpbiBhIG5pY2VyIGZvcm1hdAprbml0cjo6a2FibGUoc2FtcGxlX2RhdGEsCiAgICAgICAgICAgICBhbGlnbiA9ICJjY2NjIiwKICAgICAgICAgICAgIGZvcm1hdCA9ICJodG1sIiwKICAgICAgICAgICAgIHRhYmxlLmF0dHIgPSAic3R5bGU9J3dpZHRoOjMwJTsnIikKYGBgCgpDb252ZXJ0IHRvIGFuIE1zblNldAoKKkEgYE1zblNldGAgaXMgYSBwcm90ZW9taWNzLXNwZWNpZmljIGRhdGEgc3RydWN0dXJlLCB3aXRoIGZ1bmN0aW9ucyBhdmFpbGFibGUgZm9yIHN0YW5kYXJkIHByb3Rlb21pY3Mgd29ya2Zsb3dzLiBJdCdzIHNpbmNlIGJlZW4gZGVwcmVjYXRlZCBhbmQgcmVwbGFjZWQgYnkgYFFmZWF0dXJlc2AsIHdoaWNoIGFsbG93cyBtdWx0aXBsZSBsZXZlbHMgb2YgcXVhbnRpZmljYXRpb24gdG8gYmUgc3RvcmVkIGluIHRoZSBzYW1lIG9iamVjdCwgd2l0aCBkZXRhaWxzIGFib3V0IGhvdyB0aGUgZmVhdHVyZXMgaW4gZWFjaCBsZXZlbCBhcmUgbGlua2VkLiBOb25ldGhlbGVzcywgYSBgTXNuU2V0YCBpcyBzdGlsbCBhIHNlbnNpYmxlIGRhdGEgc3RydWN0dXJlIHRvIHVzZSBmb3IgcHJvdGVvbWljcyBkYXRhLiBNYW55IGBjYW1wcm90UmAgZnVuY3Rpb25zIGhhdmUgYmVlbiB3cml0dGVuIGV4cGxpY2l0bHkgdG8gd29yayB3aXRoIGBNc25TZXRzYCwgZm9yIGV4YW1wbGUgYHBsb3RfcXVhbnRgIGluIHRoZSBjb2RlIGJsb2NrIGFmdGVyIHRoaXMuKgoKPQoKYGBge3J9CiNzZWxlY3QgY29sdW1ucyB3aXRoIHRoZSB3b3JkICJhYnVuZGFuY2UiIGluLgpleHByc19kYXRhIDwtIHBlcF9kYXRhX2ZsdCAlPiUKICBzZWxlY3QobWF0Y2hlcygiQWJ1bmRhbmNlLi5GLioiKSkgJT4lCiAgYXMubWF0cml4KCkKCiMgY2hlY2sgdGhlIG9yZGVyIG9mIHRoZSBzYW1wbGVzLiBUaGV5IGFwcGVhciB0byBiZSBvcmRlcmVkIGFzIHJlcDEtMyBmb3IgZWFjaCBjb25kaXRpb24uCiMgSWYgdGhlIHJlcGxpY2F0ZSBkZXRhaWwgaXMgaW1wb3J0YW50LCBlLmcgcGVyZm9ybWVkIG9uIHNlcGFyYXRlIGRheXMsIGl0IHdvdWxkIGJlCiMgcHJ1ZGVudCB0byBjaGVjayB3aXRoIFNhbmRpcCBpZiB0aGV5IHdlcmUgaW5kZWVkIGFsbCBydW4gaW4gb3JkZXIgcmVwMS0zLgojIEhlIHNob3VsZCBoYXZlIHNoYXJlZCBhbiBJbnB1dEZpbGVzLnR4dCBmaWxlIHdoaWNoIHdpbGwgc3BlY2lmeSB0aGUgZnVsbCBzYW1wbGUgbmFtZSBmb3IgZWFjaCBydW4sCiMgd2hpY2ggd291bGQgaG9wZWZ1bGx5IGRldGFpbCB0aGUgcmVwbGljYXRlIG51bWJlcgpwcmludChjb2xuYW1lcyhleHByc19kYXRhKSkKCiMgUmVtb3ZlIHRoZSB1bndhbnRlZCBwYXJ0IG9mIHRoZSBjb2x1bW4gbmFtZQpjb2xuYW1lcyhleHByc19kYXRhKSA8LSBnc3ViKCdBYnVuZGFuY2UuLi4qLi5TYW1wbGUuLicsICcnLCBjb2xuYW1lcyhleHByc19kYXRhKSkKCiMgQWRkIHRoZSByZXBsaWNhdGUgbnVtYmVyCmNvbG5hbWVzKGV4cHJzX2RhdGEpIDwtIHBhc3RlKGNvbG5hbWVzKGV4cHJzX2RhdGEpLCByZXAoYygxLDIsMyksIDYpLCBzZXA9J18nKQoKIyBDcmVhdGUgZGF0YS5mcmFtZSB3aXRoIHNhbXBsZSBtZXRhZGF0YSAocERhdGEpCiN0YWtlcyBzYW1wbGUgZGF0YSBhbmQgc2VsZWN0cyBldmVyeSBjb2x1bW4gd2l0aG91dCAiZmlsZSIgaW4gdGhlIG5hbWUsIHRoZW4gY3JlYXRlcyBhIHRpYmJsZSB3aXRoIHRoZSByb3duYW1lcyBhcyB0aGUgY29sdW1uICJzYW1wbGUiCnBoZW5vX2RhdGEgPC0gc2FtcGxlX2RhdGEgJT4lCiAgc2VsZWN0KC1GaWxlKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiU2FtcGxlIikKCgojIFJlb3JkZXIgdGhlIHBoZW5vdHlwZSBkYXRhIHRvIGJlIGluIHRoZSBzYW1lIG9yZGVyIGFzIHRoZSBleHBycyBtYXRyaXgKIyBFeGFtcGxlIGRhdGFmcmFtZQoKIyBEZWZpbmUgdGhlIGRlc2lyZWQgb3JkZXIgb2YgY29sdW1ucwpkZXNpcmVkX29yZGVyIDwtIGMoIkNMX0lPTl8xIiwiQ0xfSU9OXzIiLCJDTF9JT05fMyIsICJDTF9MTkFfMSIsIkNMX0xOQV8yIiwiQ0xfTE5BXzMiLCAiQ0xfVkVIXzEiLCJDTF9WRUhfMiIsIkNMX1ZFSF8zIiwiTkNMX0lPTl8xIiwiTkNMX0lPTl8yIiwiTkNMX0lPTl8zIiwgIk5DTF9MTkFfMSIsIk5DTF9MTkFfMiIsIk5DTF9MTkFfMyIsICJOQ0xfVkVIXzEiLCJOQ0xfVkVIXzIiLCJOQ0xfVkVIXzMiKQoKIyBTdWJzZXQgdGhlIGRhdGFmcmFtZSB3aXRoIHRoZSBkZXNpcmVkIGNvbHVtbiBvcmRlcgpwaGVub19kYXRhIDwtIHBoZW5vX2RhdGFbZGVzaXJlZF9vcmRlcixdCgoKCiMgQ3JlYXRlIGRhdGEuZnJhbWUgd2l0aCBwZXB0aWRlIG1ldGFkYXRhIChmRGF0YSkKIyBzZWxlY3QgYWxsIGNvbHVtbnMgZXhjZXB0IHRob3NlIHdpdGggYWJ1bmRhbmNlIGluIHRoZSBuYW1lLgpmZWF0dXJlX2RhdGEgPC0gcGVwX2RhdGFfZmx0ICU+JQogICMgVGhpcyByZXRhaW5zIGFsbCBjb2x1bW5zIGV4Y2VwdCB0aGUgYWJ1bmRhbmNlIGNvbHVtbnMuIFdlIGRvbid0IHJlYWxseSBuZWVkIG1vc3QgY29sdW1ucwogICNzZWxlY3QoLW1hdGNoZXMoIkFidW5kYW5jZSIpKSAKICAjIFRoaXMganVzdCByZXRhaW5zIHRoZSB1c2VmdWwgY29sdW1ucwogIHNlbGVjdChBbm5vdGF0ZWQuU2VxdWVuY2UsIE1vZGlmaWNhdGlvbnMsIFByb3RlaW4uQWNjZXNzaW9ucywgTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykKCgojIENyZWF0ZSBNU25TZXQKcGVwIDwtIE1TbmJhc2U6Ok1TblNldChleHBycyA9IGV4cHJzX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgZkRhdGEgPSBmZWF0dXJlX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgcERhdGEgPSBwaGVub19kYXRhKQoKCmBgYAoKCkxldCdzIFFDIHRoZSBwZXB0aWRlcwoKYGBge3J9CnBlcCAlPiUKICBsb2coYmFzZSA9IDIpICU+JQogIGNhbXByb3RSOjpwbG90X3F1YW50KG1ldGhvZCA9ICdib3gnKQpgYGAKCmxldCdzIGxvb2sgYXQgc29tZSBjb29sIHBlcHRpZGUgaW50ZW5zaXRpZXMKCmBgYHtyfQpwZXAgJT4lCiAgbG9nKGJhc2UgPSAyKSAlPiUKICBjYW1wcm90Ujo6cGxvdF9xdWFudChtZXRob2QgPSAnZGVuc2l0eScpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1yZXAoZ2V0X2NhdF9wYWxldHRlKDYpLCBlYWNoPTMpKQpgYGAKCmBgYHtyfQpwIDwtIE1TbmJhc2U6OnBsb3ROQShwZXAsIHBOQSA9IDApICsKICBjYW1wcm90Ujo6dGhlbWVfY2FtcHJvdChib3JkZXIgPSBGQUxTRSwgYmFzZV9mYW1pbHkgPSAnc2FucycsIGJhc2Vfc2l6ZSA9IDEwKSArCiAgbGFicyh4ID0gJ1BlcHRpZGUgaW5kZXgnKQpgYGAKCipuYW5pYXI6OmdnX21pc3NfdXBzZXQgaXMgYSBnZW5lcmljIGZ1bmN0aW9uLCBub3Qgb25lIHNwZWNpZmljYWxseSBmb3IgcHJvdGVvbWljcyBkYXRhLiBBcyBzdWNoLCBpdCB3YXMgZGVzaWduZWQgdG8gdGFrZSBhIGRhdGEuZnJhbWUoKSB3aXRoIGp1c3QgbnVtZXJpYyBjb2x1bW5zLiBIZW5jZSB3ZSBleHRyYWN0IHRoZSBleHBycyBtYXRyaXggZnJvbSB0aGUgTXNuU2V0IGFuZCBjb252ZXJ0IHRvIGEgZGF0YS5mcmFtZSoKCmBgYHtyfQptaXNzaW5nX2RhdGEgPC0gcGVwICU+JQogIGV4cHJzKCkgJT4lCiAgZGF0YS5mcmFtZSgpCgpuYW5pYXI6OmdnX21pc3NfdXBzZXQobWlzc2luZ19kYXRhLAogICAgICAgICAgICAgICAgICAgICAgc2V0cyA9IHBhc3RlMChjb2xuYW1lcyhwZXApLCAnX05BJyksCiAgICAgICAgICAgICAgICAgICAgICBrZWVwLm9yZGVyID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIG5zZXRzID0gMTApCmBgYAoKSSBEb24ndCB0aGluayB3ZSBuZWVkIHRvIGRvIGFueSBub3JtYWxpc2luZyB0byBwZXB0aWRlIGludGVuc2l0aWVzIGJlY2F1c2Ugd2UgZGlkbid0IGluamVjdCB0aGUgc2FtZSBhbW91bnQgb2YgcGVwdGlkZSwgaXQncyBhIHB1bGxkb3duLgoKTGV0J3MgZG8gc29tZSBzdW1tYXJpc2luZyB0byBwcm90ZWluLWxldmVsIGFidW5kYW5jZXMKCipSZWxheGluZyBtYXggTkEgdG8gMi8zIGFuZCByZWR1Y2luZyBtaW4gcGVwdGlkZXMgcGVyIHByb3RlaW4gdG8gMiAoZnJvbSAzKSBkb3VibGVzIHRoZSByZXRhaW5lZCBwZXB0aWRlcyB0byBcfjIwMDAuKgoKYGBge3J9CgpwZXBfcmVzdHJpY3RlZCA8LSBwZXAgJT4lCiAgIyBNYXhpbXVtIDIvMyBtaXNzaW5nIHZhbHVlcwogIE1TbmJhc2U6OmZpbHRlck5BKHBOQSA9IDIvMykgJT4lICMgV2l0aCAxOCBzYW1wbGUsIGFsbG93aW5nIG1pc3NpbmcgaW4gMTIgc2VlbXMgcmVhc29uYWJsZSB0byBtZS4KCiAgIyBBdCBsZWFzdCB0d28gcGVwdGlkZXMgcGVyIHByb3RlaW4KICBjYW1wcm90Ujo6cmVzdHJpY3RfZmVhdHVyZXNfcGVyX3Byb3RlaW4obWluX2ZlYXR1cmVzID0gMiwgcGxvdCA9IEZBTFNFKSAlPiUKCiAgIyBSZXBlYXQgdGhlIGZpbHRlcmluZyBzaW5jZSByZXN0cmljdF9mZWF0dXJlc19wZXJfcHJvdGVpbiB3aWxsIHJlcGxhY2Ugc29tZSB2YWx1ZXMgd2l0aCBOQQogIE1TbmJhc2U6OmZpbHRlck5BKHBOQSA9IDIvMykgJT4lCgogIGNhbXByb3RSOjpyZXN0cmljdF9mZWF0dXJlc19wZXJfcHJvdGVpbihtaW5fZmVhdHVyZXMgPSAyLCBwbG90ID0gRkFMU0UpCmBgYAoKYGBge3J9CnAgPC0gTVNuYmFzZTo6cGxvdE5BKHBlcF9yZXN0cmljdGVkLCBwTkEgPSAwKSArCiAgY2FtcHJvdFI6OnRoZW1lX2NhbXByb3QoYm9yZGVyID0gRkFMU0UsIGJhc2VfZmFtaWx5ID0gJ3NhbnMnLCBiYXNlX3NpemUgPSAxNSkgKwogIGxhYnMoeCA9ICdQZXB0aWRlIGluZGV4JykKYGBgCgoqV2FybmluZyBtZXNzYWdlIHJlIG1pc3NpbmcgdmFsdWVzIGNhbiBiZSBzYWZlbHkgaWdub3JlZCBoZXJlLiBgTVNuYmFzZTo6Y29tYmluZUZlYXR1cmVzYCBkb2Vzbid0IGRvIGFueSBzYW5pdHkgY2hlY2tpbmcgdGhhdCBOQSB2YWx1ZXMgYXJlIGJlaW5nIGFwcHJvcHJpYXRlbHkgaGFuZGxlZCBhbmQgZ2l2ZXMgdGhpcyB3YXJuaW5nIHNvIHRoZSBvbnVzIGlzIG9uIHRoZSB1c2VyIHRvIGtub3cgd2hhdCB0aGV5IGFyZSBkb2luZy4gYG1ldGhvZD0ncm9idXN0J2AgY2FuIGhhbmRsZSBOQSB2YWx1ZXMgYXBwcm9wcmlhdGVseS4gQW55IHByb3RlaW5zIHdpdGggTkEgdmFsdWUgZm9sbG93aW5nIHRoaXMgd2lsbCBiZSBiZWNhdXNlIHRoZSBtb2RlbCBjb3VsZCBub3QgZXN0aW1hdGUgcHJvdGVpbiBhYnVuZGFuY2UgZnJvbSB0aGUgcGVwdGlkZSBhYnVuZGFuY2UuIEZvciBleGFtcGxlLCBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLCB3aGVyZSBYID0gcGVwdGlkZSBxdWFudGlmaWVkLCB0aGUgcHJvdGVpbiBjYW5ub3QgYmUgcXVhbnRpZmllZCBpbiBzYW1wbGUgQSwgc2luY2UgdGhlIHBlcHRpZGVzIHF1YW50aWZpZWQgaW4gc2FtcGxlIEEgYXJlIG5vdCBxdWFudGlmaWVkIGluIGFueSBvdGhlciBzYW1wbGUuIFRoaXMgaXMgd2h5IHdlIG5lZWQgdG8gcHJlLWZpbHRlciB0aGUgcGVwdGlkZXMgdG8gb25seSByZXRhaW4gdGhlIG1vc3QgaW5mb3JtYXRpdmUgb25lcyoKCnwgICAgICB8IEEgICB8IEIgICB8IEMgICB8IEQgICB8CnwtLS0tLS18LS0tLS18LS0tLS18LS0tLS18LS0tLS18CnwgcGVwMSB8IFggICB8ICAgICB8ICAgICB8ICAgICB8CnwgcGVwMiB8ICAgICB8IFggICB8IFggICB8IFggICB8CnwgcGVwMyB8IFggICB8ICAgICB8ICAgICB8ICAgICB8CnwgcGVwNCB8ICAgICB8IFggICB8IFggICB8IFggICB8CgpgYGB7cn0KCnByb3Rfcm9idXN0IDwtIHBlcF9yZXN0cmljdGVkICU+JQogIGxvZyhiYXNlPTIpICU+JQogIE1TbmJhc2U6OmNvbWJpbmVGZWF0dXJlcygKICAgICMgZ3JvdXAgdGhlIHBlcHRpZGVzIGJ5IHRoZWlyIG1hc3RlciBwcm90ZWluIGlkCiAgICBncm91cEJ5ID0gZkRhdGEocGVwX3Jlc3RyaWN0ZWQpJE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsCiAgICBtZXRob2QgPSAncm9idXN0JywKICAgIG1heGl0ID0gMTAwMCAgIyBFbnN1cmVzIGNvbnZlcmdlbmNlIGZvciBNQVNTOjpybG0KICApCgpgYGAKCmBgYHtyfQpwIDwtIE1TbmJhc2U6OnBsb3ROQShwcm90X3JvYnVzdCwgcE5BID0gMCkgKwogIGNhbXByb3RSOjp0aGVtZV9jYW1wcm90KGJvcmRlciA9IEZBTFNFLCBiYXNlX2ZhbWlseSA9ICdzYW5zJywgYmFzZV9zaXplID0gMTUpCmBgYAoKYGBge3J9Cm5hbmlhcjo6Z2dfbWlzc191cHNldChkYXRhLmZyYW1lKGV4cHJzKHByb3Rfcm9idXN0KSksCiAgICAgICAgICAgICAgICAgICAgICBzZXRzID0gcGFzdGUwKGNvbG5hbWVzKHByb3Rfcm9idXN0KSwgJ19OQScpLAogICAgICAgICAgICAgICAgICAgICAga2VlcC5vcmRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBuc2V0cyA9IDEwKQpgYGAKCmBgYHtyfQpzYXZlUkRTKHByb3Rfcm9idXN0LCAnbGZxX3Byb3Rfcm9idXN0LnJkcycpCmBgYAoKU2hhbGwgd2UgZG8gc29tZSBzdGF0aXN0aWNhbCB0ZXN0aW5nPwoKTG9hZCBpbiB0aGUgUUMnZCBMRlEgZGF0YSBmcm9tIHRoZSByZHMgZnJvbSBiZWZvcmUKCmBgYHtyfQpsZnFfcHJvdGVpbiA8LSByZWFkUkRTKCdsZnFfcHJvdF9yb2J1c3QucmRzJykKYGBgCgoqV2UgbmVlZCB0byBjb252ZXJ0IHRoZSBkYXRhIHN0cnVjdHVyZSBmcm9tIE1zblNldCB0byBhIGRhdGEuZnJhbWUgdG8gbWFrZSBpdCBlYXN5IHRvIGZpbHRlciB0byBwcm90ZWlucyB3aXRoIGF0IGxlYXN0IHR3byByZXBsaWNhdGUgd2l0aCBxdWFudGlmaWNhdGlvbiB2YWx1ZXMgZm9yIGVhY2ggc2V0IG9mIHNhbXBsZXMuIEl0IHdvdWxkIGJlIHBvc3NpYmxlIHRvIGRvIHRoaXMgd2l0aCB0aGUgTXNuU2V0LCB0aG91Z2ggSSB0aGluayBpdCB3b3VsZCBwcm92ZSBwcmV0dHkgcGFpbmZ1bCoKCk5vdGUsIGNvZGUgYmVsb3cgaXMgYW4gZXhhbXBsZSBmb3IgaG93IHRvIHRlc3QgQ0wgdnMgTkMgZm9yIHRoZSA2IExOQSBzYW1wbGVzLgpJZiB5b3UgYXJlIHBlcmZvcm1pbmcgbWFueSBkaWZmZXJlbnQgY29tcGFyaXNvbnMsIGl0IG1heSBtYWtlIHNlbnNlIHRvIGNyZWF0ZSBhIGZ1bmN0aW9uKHMpIGZvciBzb21lIG9mIHRoaXMKdG8gYXZvaWQgcmVwZWF0aW5nIHRoZSBzYW1lIGNvZGUgYW5kIGhhdmluZyBsb3RzIG9mIHNpbWlsYXJseSBuYW1lZCBvYmplY3RzIGFyb3VuZC4gSSd2ZSBpbmRpY2F0ZWQgdGhlIG1vc3Qgb2J2aW91cyBwYXJ0cyB3aGVyZSBmdW5jdGlvbnMgY291bGQgYmUgd3JpdHRlbi4KIC0gCiAtIGFkZGluZyB0aGUgY291bnRzIHRvIHRoZSBsaW1tYSBvYmplY3QKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLVRlc3QgTE5BIENMIHZzIE5DTCBkYXRhLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgpgYGB7cn0KCmxmcV9wcm90ZWluX3RpZHlfbG5hIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBzdWJzZXQoc2FtcGxlPT0iTkNMX0xOQV8xInwgc2FtcGxlPT0iTkNMX0xOQV8yIiB8IHNhbXBsZT09Ik5DTF9MTkFfMyIgfCBzYW1wbGU9PSJDTF9MTkFfMSJ8IHNhbXBsZT09IkNMX0xOQV8yIiB8IHNhbXBsZT09IkNMX0xOQV8zIikgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZSh2YWx1ZSkpICU+JQogIGdyb3VwX2J5KHByb3RlaW4sIENvbmRpdGlvbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKENvbmRpdGlvbikpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19sbmEgPC0gbGZxX3Byb3RlaW5fdGlkeV9sbmEgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1zYW1wbGUsIHZhbHVlc19mcm9tPXZhbHVlLCBpZF9jb2xzPXByb3RlaW4pICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCdwcm90ZWluJykgJT4lCiAgYXMubWF0cml4KCkKCiMgU2luY2UgdGhlIGNvbHVtbiBuYW1lcyBmb3Igb3VyIGZpbHRlcmVkIGV4cHJzIG1hdHJpeCBpcyBpbiB0aGUgc2FtZSBmb3JtYXQgYXMgbGZxX3Byb3RlaW4KIyB3ZSBjYW4gc3RpbGwgdXNlIHRoZSBwaGVub0RhdGEgZm9yIGxmcV9wcm90ZWluIHRvIGRlZmluZSB0aGUgY29uZGl0aW9uIGFuZAojIHJlcGxpY2F0ZSB2ZWN0b3JzLCBzbyBsb25nIGFzIHdlIHJlLW9yZGVyIGxmcV9wcm90ZWluIHVzaW5nIHRoZSBleHBycyBtYXRyaXgKIyBjb2x1bW4gbmFtZXMgZmlyc3QKdHJlYXRtZW50IDwtIHBEYXRhKGxmcV9wcm90ZWluWyxjb2xuYW1lcyhmaWx0ZXJlZF9leHByc19sbmEpXSkkQ29uZGl0aW9uCnRyZWF0bWVudCA8LSBmYWN0b3IodHJlYXRtZW50LCBsZXZlbHMgPSBjKCdDTCcsICdOQ0wnKSkKCmxpbW1hX2Rlc2lnbl9sbmEgPC0gbW9kZWwubWF0cml4KGZvcm11bGEofnRyZWF0bWVudCkpCgpsaW1tYV9maXRfbG5hIDwtIGxtRml0KGZpbHRlcmVkX2V4cHJzX2xuYSwgbGltbWFfZGVzaWduX2xuYSkKbGltbWFfZml0X2xuYSA8LSBlQmF5ZXMobGltbWFfZml0X2xuYSwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2xuYSkKCiMgVGhlIG5leHQgdHdvIHN0ZXBzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgdGhleSB3aWxsIGFsd2F5cyBiZSB0aGUgc2FtZQojIGdldF9taW5fcGVwdGlkZXMgPC0gZnVuY3Rpb24oZmlsdGVyZWRfd2lkZV9leHByKSB7fQpmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2xuYSA8LSBmaWx0ZXJlZF9leHByc19sbmEgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9sbmEgPC0gY2FtcHJvdFI6OmNvdW50X2ZlYXR1cmVzX3Blcl9wcm90ZWluKHBlcCkgJT4lCiAgbWVyZ2UoZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19sbmEsIGJ5PWMoJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnLCAnc2FtcGxlJykpICU+JQogIAogIGdyb3VwX2J5KE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMpICU+JQogIHN1bW1hcmlzZShtaW5fcGVwX2NvdW50ID0gbWluKG4pKQojIyMKCgojIGFkZCB0aGUgbWluIHBlcHRpZGUgY291bnQKbGltbWFfZml0X2xuYSRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2xuYSRtaW5fcGVwX2NvdW50CgplZml0X2RlcW1zX2xuYSA8LSBzdXBwcmVzc1dhcm5pbmdzKHNwZWN0cmFDb3VudGVCYXllcyhsaW1tYV9maXRfbG5hKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2xuYSwgbiA9IDMwLCB4bGFiID0gIlBlcHRpZGVzIikKCmRlcW1zX3Jlc3VsdHNfbG5hIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2xuYSwgY29lZl9jb2w9MikKYGBgCgoKYGBge3J9Cmhpc3QoZGVxbXNfcmVzdWx0c19sbmEkUC5WYWx1ZSkKaGlzdChkZXFtc19yZXN1bHRzX2xuYSRzY2EuUC5WYWx1ZSkKCmBgYAoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19sbmEkc2NhLmFkai5wdmE8MC4xKQoKZGVxbXNfcmVzdWx0c19sbmEgJT4lIGZpbHRlcihsb2dGQz4wKSAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2xuYSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChzY2EuUC5WYWx1ZSksIGNvbG91ciA9IHNjYS5hZGoucHZhbCA8IDAuMSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2NhbXByb3QoYm9yZGVyPUZBTFNFLCBiYXNlX3NpemU9MTUpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZXknLCBnZXRfY2F0X3BhbGV0dGUoMilbMl0pLCBuYW1lID0gJ0NMIHZzIE5DTCBTaWcuJykgKwogIGxhYnMoeCA9ICdMTkEgQ0wvTkNMJywgeSA9ICctbG9nMTAocC12YWx1ZSknKQpgYGAKCgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1UZXN0IElPTiBDTCB2cyBOQ0wgZGF0YS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKYGBge3J9CgpsZnFfcHJvdGVpbl90aWR5X2lvbiA8LSBsZnFfcHJvdGVpbiAlPiUKICAjIG1ha2UgdGhlIE1TblNldCBpbnRvICd0aWR5JyBmb3JtYXQgZm9yIGZ1cnRoZXIgdGVzdGluZwogIGJpb2Jyb29tOjp0aWR5Lk1TblNldChhZGRQaGVubz1UUlVFKSAlPiUgIyBhZGRQaGVubz1UUlVFIGFkZHMgdGhlIHBoZW5vdHlwZSBjb2x1bW5zCiAgc3Vic2V0KHNhbXBsZT09Ik5DTF9JT05fMSJ8IHNhbXBsZT09Ik5DTF9JT05fMiIgfCBzYW1wbGU9PSJOQ0xfSU9OXzMiIHwgc2FtcGxlPT0iQ0xfSU9OXzEifCBzYW1wbGU9PSJDTF9JT05fMiIgfCBzYW1wbGU9PSJDTF9JT05fMyIpICU+JQogICMgVFM6IHdvdWxkIGJlIGJlc3QgdG8gaGF2ZSBhIGNvbHVtbiBpbiB0aGUgcERhdGEgd2hpY2ggZGVzY3JpYmVzIHRoZSBWRUgvTE5BL0lPTiB2YXJpYWJsZSwgc28geW91IGNhbiBqdXN0IGZpbHRlciB1c2luZyB0aGF0IGNvbHVtbgogIHN1YnNldChzYW1wbGUgJWluJSBjKCJOQ0xfSU9OXzEiLCAiTkNMX0lPTl8yIiwgIk5DTF9JT05fMyIsICJDTF9JT05fMSIsICJDTF9JT05fMiIsICJDTF9JT05fMyIpKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbiwgQ29uZGl0aW9uKSAlPiUKICBmaWx0ZXIobigpPj0yKSAlPiUgCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKGxlbmd0aCh1bmlxdWUoQ29uZGl0aW9uKSk9PTIpICU+JSMgbigpIGlzIHRoZSBsZW5ndGggb2YgdGhlIGdyb3VwCiAgdW5ncm91cCgpCgogIAojIFRoaXMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSBpdCBhbHdheXMgYmUgdGhlIHNhbWUgc3RlcHMKIyBtYWtlX2V4cHJfd2lkZSA8LSBmdW5jdGlvbih0aWR5X2V4cHIpIHt9CmZpbHRlcmVkX2V4cHJzX2lvbiA8LSBsZnFfcHJvdGVpbl90aWR5X2lvbiAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAp0cmVhdG1lbnQgPC0gcERhdGEobGZxX3Byb3RlaW5bLGNvbG5hbWVzKGZpbHRlcmVkX2V4cHJzX2lvbildKSRDb25kaXRpb24KdHJlYXRtZW50IDwtIGZhY3Rvcih0cmVhdG1lbnQsIGxldmVscyA9IGMoJ0NMJywgJ05DTCcpKQoKbGltbWFfZGVzaWduX2lvbiA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+dHJlYXRtZW50KSkKCmxpbW1hX2ZpdF9pb24gPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfaW9uLCBsaW1tYV9kZXNpZ25faW9uKQpsaW1tYV9maXRfaW9uIDwtIGVCYXllcyhsaW1tYV9maXRfaW9uLCB0cmVuZD1UUlVFKQoKbGltbWE6OnBsb3RTQShsaW1tYV9maXRfaW9uKQoKIyBUaGUgbmV4dCB0d28gc3RlcHMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSB0aGV5IHdpbGwgYWx3YXlzIGJlIHRoZSBzYW1lCiMgZ2V0X21pbl9wZXB0aWRlcyA8LSBmdW5jdGlvbihmaWx0ZXJlZF93aWRlX2V4cHIpIHt9CmZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfaW9uIDwtIGZpbHRlcmVkX2V4cHJzX2lvbiAlPiUKICBkYXRhLmZyYW1lKCkgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz0tTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucywgdmFsdWVzX3RvPSdhYnVuZGFuY2UnLCBuYW1lc190bz0nc2FtcGxlJykgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZShhYnVuZGFuY2UpKSAjIFdlIG9ubHkgd2FudCB0byBjb25zaWRlciBzYW1wbGVzIHdpdGggYSByYXRpbyBxdWFudGlmaWVkCgptaW5fcGVwX2NvdW50X2lvbiA8LSBjYW1wcm90Ujo6Y291bnRfZmVhdHVyZXNfcGVyX3Byb3RlaW4ocGVwKSAlPiUKICBtZXJnZShmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2lvbiwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfaW9uJGNvdW50IDwtIG1pbl9wZXBfY291bnRfaW9uJG1pbl9wZXBfY291bnQKCmVmaXRfZGVxbXNfaW9uIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9pb24pKQoKVmFyaWFuY2VCb3hwbG90KGVmaXRfZGVxbXNfaW9uLCBuID0gMzAsIHhsYWIgPSAiUGVwdGlkZXMiKQoKZGVxbXNfcmVzdWx0c19pb24gPC0gb3V0cHV0UmVzdWx0KGVmaXRfZGVxbXNfaW9uLCBjb2VmX2NvbD0yKQpgYGAKCgpgYGB7cn0KaGlzdChkZXFtc19yZXN1bHRzX2lvbiRQLlZhbHVlKQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uJHNjYS5QLlZhbHVlKQoKYGBgCgoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19pb24kc2NhLmFkai5wdmE8MC4xKQoKZGVxbXNfcmVzdWx0c19pb24gJT4lIGZpbHRlcihsb2dGQz4wKSAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2lvbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChzY2EuUC5WYWx1ZSksIGNvbG91ciA9IHNjYS5hZGoucHZhbCA8IDAuMSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2NhbXByb3QoYm9yZGVyPUZBTFNFLCBiYXNlX3NpemU9MTUpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZXknLCBnZXRfY2F0X3BhbGV0dGUoMilbMl0pLCBuYW1lID0gJ0NMIHZzIE5DTCBTaWcuJykgKwogIGxhYnMoeCA9ICdJT04gQ0wvTkNMJywgeSA9ICctbG9nMTAocC12YWx1ZSknKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1UZXN0IFZFSCBDTCB2cyBOQ0wgZGF0YS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKYGBge3J9CgpsZnFfcHJvdGVpbl90aWR5X3ZlaCA8LSBsZnFfcHJvdGVpbiAlPiUKICAjIG1ha2UgdGhlIE1TblNldCBpbnRvICd0aWR5JyBmb3JtYXQgZm9yIGZ1cnRoZXIgdGVzdGluZwogIGJpb2Jyb29tOjp0aWR5Lk1TblNldChhZGRQaGVubz1UUlVFKSAlPiUgIyBhZGRQaGVubz1UUlVFIGFkZHMgdGhlIHBoZW5vdHlwZSBjb2x1bW5zCiAgc3Vic2V0KHNhbXBsZT09Ik5DTF9WRUhfMSJ8IHNhbXBsZT09Ik5DTF9WRUhfMiIgfCBzYW1wbGU9PSJOQ0xfVkVIXzMiIHwgc2FtcGxlPT0iQ0xfVkVIXzEifCBzYW1wbGU9PSJDTF9WRUhfMiIgfCBzYW1wbGU9PSJDTF9WRUhfMyIpICU+JQogIGZpbHRlcihpcy5maW5pdGUodmFsdWUpKSAlPiUKICBncm91cF9ieShwcm90ZWluLCBDb25kaXRpb24pICU+JQogIGZpbHRlcihuKCk+PTIpICU+JSAKICBncm91cF9ieShwcm90ZWluKSAlPiUKICBmaWx0ZXIobGVuZ3RoKHVuaXF1ZShDb25kaXRpb24pKT09MikgJT4lIyBuKCkgaXMgdGhlIGxlbmd0aCBvZiB0aGUgZ3JvdXAKICB1bmdyb3VwKCkKCiAgCiMgVGhpcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIGl0IGFsd2F5cyBiZSB0aGUgc2FtZSBzdGVwcwojIG1ha2VfZXhwcl93aWRlIDwtIGZ1bmN0aW9uKHRpZHlfZXhwcikge30KZmlsdGVyZWRfZXhwcnNfdmVoIDwtIGxmcV9wcm90ZWluX3RpZHlfdmVoICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209c2FtcGxlLCB2YWx1ZXNfZnJvbT12YWx1ZSwgaWRfY29scz1wcm90ZWluKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygncHJvdGVpbicpICU+JQogIGFzLm1hdHJpeCgpCgojIFNpbmNlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIG91ciBmaWx0ZXJlZCBleHBycyBtYXRyaXggaXMgaW4gdGhlIHNhbWUgZm9ybWF0IGFzIGxmcV9wcm90ZWluCiMgd2UgY2FuIHN0aWxsIHVzZSB0aGUgcGhlbm9EYXRhIGZvciBsZnFfcHJvdGVpbiB0byBkZWZpbmUgdGhlIGNvbmRpdGlvbiBhbmQKIyByZXBsaWNhdGUgdmVjdG9ycywgc28gbG9uZyBhcyB3ZSByZS1vcmRlciBsZnFfcHJvdGVpbiB1c2luZyB0aGUgZXhwcnMgbWF0cml4CiMgY29sdW1uIG5hbWVzIGZpcnN0CnRyZWF0bWVudCA8LSBwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfdmVoKV0pJENvbmRpdGlvbgp0cmVhdG1lbnQgPC0gZmFjdG9yKHRyZWF0bWVudCwgbGV2ZWxzID0gYygnQ0wnLCAnTkNMJykpCgpsaW1tYV9kZXNpZ25fdmVoIDwtIG1vZGVsLm1hdHJpeChmb3JtdWxhKH50cmVhdG1lbnQpKQoKbGltbWFfZml0X3ZlaCA8LSBsbUZpdChmaWx0ZXJlZF9leHByc192ZWgsIGxpbW1hX2Rlc2lnbl92ZWgpCmxpbW1hX2ZpdF92ZWggPC0gZUJheWVzKGxpbW1hX2ZpdF92ZWgsIHRyZW5kPVRSVUUpCgpsaW1tYTo6cGxvdFNBKGxpbW1hX2ZpdF92ZWgpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ192ZWggPC0gZmlsdGVyZWRfZXhwcnNfdmVoICU+JQogIGRhdGEuZnJhbWUoKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbignTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPS1NYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zLCB2YWx1ZXNfdG89J2FidW5kYW5jZScsIG5hbWVzX3RvPSdzYW1wbGUnKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKGFidW5kYW5jZSkpICMgV2Ugb25seSB3YW50IHRvIGNvbnNpZGVyIHNhbXBsZXMgd2l0aCBhIHJhdGlvIHF1YW50aWZpZWQKCm1pbl9wZXBfY291bnRfdmVoIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfdmVoLCBieT1jKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJywgJ3NhbXBsZScpKSAlPiUKICAKICBncm91cF9ieShNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zKSAlPiUKICBzdW1tYXJpc2UobWluX3BlcF9jb3VudCA9IG1pbihuKSkKIyMjCgoKIyBhZGQgdGhlIG1pbiBwZXB0aWRlIGNvdW50CmxpbW1hX2ZpdF92ZWgkY291bnQgPC0gbWluX3BlcF9jb3VudF92ZWgkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc192ZWggPC0gc3VwcHJlc3NXYXJuaW5ncyhzcGVjdHJhQ291bnRlQmF5ZXMobGltbWFfZml0X3ZlaCkpCgpWYXJpYW5jZUJveHBsb3QoZWZpdF9kZXFtc192ZWgsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpkZXFtc19yZXN1bHRzX3ZlaCA8LSBvdXRwdXRSZXN1bHQoZWZpdF9kZXFtc192ZWgsIGNvZWZfY29sPTIpCmBgYAoKCmBgYHtyfQpoaXN0KGRlcW1zX3Jlc3VsdHNfdmVoJFAuVmFsdWUpCmhpc3QoZGVxbXNfcmVzdWx0c192ZWgkc2NhLlAuVmFsdWUpCgpgYGAKCgpgYGB7cn0KCnRhYmxlKGRlcW1zX3Jlc3VsdHNfdmVoJHNjYS5hZGoucHZhPDAuMSkKCmRlcW1zX3Jlc3VsdHNfdmVoICU+JSBmaWx0ZXIobG9nRkM+MCkgJT4lIGhlYWQoKQoKZGVxbXNfcmVzdWx0c192ZWggJT4lCiAgZ2dwbG90KGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAoc2NhLlAuVmFsdWUpLCBjb2xvdXIgPSBzY2EuYWRqLnB2YWwgPCAwLjEpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9jYW1wcm90KGJvcmRlcj1GQUxTRSwgYmFzZV9zaXplPTE1KSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCdncmV5JywgZ2V0X2NhdF9wYWxldHRlKDIpWzJdKSwgbmFtZSA9ICdDTCB2cyBOQ0wgU2lnLicpICsKICBsYWJzKHggPSAnVkVIX0NML05DTCcsIHkgPSAnLWxvZzEwKHAtdmFsdWUpJykKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLVRlc3QgSU9OIENMIHZzIExOQSBDTCBkYXRhLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkxldCdzIHZpc3VhbGlzZSB0aGUgYWJ1bmRhbmNlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlIHNhbXBsZXMgaW4gcXVlc3Rpb24uCmBgYHtyfQpwbG90X3F1YW50KGxmcV9wcm90ZWluWyxncmVwbCgnXkNMXyhJT058TE5BKV9cXGQnLCBjb2xuYW1lcyhsZnFfcHJvdGVpbikpXSwgbWV0aG9kPSdkZW5zaXR5JykgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPXJlcChnZXRfY2F0X3BhbGV0dGUoMiksIGVhY2g9MykpCnBsb3RfcXVhbnQobGZxX3Byb3RlaW5bLGdyZXBsKCdeQ0xfKElPTnxMTkEpX1xcZCcsIGNvbG5hbWVzKGxmcV9wcm90ZWluKSldLCBtZXRob2Q9J2JveCcpCmBgYAoKCgpgYGB7cn0KbGZxX3Byb3RlaW5fdGlkeV9JT05fTE5BIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBmaWx0ZXIoZ3JlcGwoJ15DTF8oSU9OfExOQSlfXFxkJywgc2FtcGxlKSkgJT4lCiAgc2VwYXJhdGUoc2FtcGxlLCBpbnRvPWMoTkEsICdUcmVhdG1lbnQnLCBOQSksIHJlbW92ZSA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKFRyZWF0bWVudCkpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19pb25fbG5hX2NsIDwtIGxmcV9wcm90ZWluX3RpZHlfSU9OX0xOQSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAoKbGlicmFyeSh0aWJibGUpCgpwaGVub19kYXRhMjwtIHJvd25hbWVzX3RvX2NvbHVtbihwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfaW9uX2xuYV9jbCldKSwgdmFyID0gInNhbXBsZSIpCnBoZW5vX2RhdGEyIDwtIHBoZW5vX2RhdGEyICU+JSBzZXBhcmF0ZShzYW1wbGUsIGludG89YyhOQSwgJ1RyZWF0bWVudCcsIE5BKSwgcmVtb3ZlID0gRkFMU0UpIAoKdHJlYXRtZW50MiA8LSBwaGVub19kYXRhMiRUcmVhdG1lbnQKdHJlYXRtZW50MiA8LSBmYWN0b3IodHJlYXRtZW50MiwgbGV2ZWxzID0gYygnSU9OJywgJ0xOQScpKQpyZXBsaWNhdGUgPC0gcGhlbm9fZGF0YTIkUmVwbGljYXRlCgpsaW1tYV9kZXNpZ25faW9uX2xuYV9jbCA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+cmVwbGljYXRlK3RyZWF0bWVudDIpKQoKbGltbWFfZml0X2lvbl9sbmFfY2wgPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfaW9uX2xuYV9jbCwgbGltbWFfZGVzaWduX2lvbl9sbmFfY2wpCmxpbW1hX2ZpdF9pb25fbG5hX2NsIDwtIGVCYXllcyhsaW1tYV9maXRfaW9uX2xuYV9jbCwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2lvbl9sbmFfY2wpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19pb25fbG5hX2NsIDwtIGZpbHRlcmVkX2V4cHJzX2lvbl9sbmFfY2wgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9pb25fbG5hX2NsIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfaW9uX2xuYV9jbCwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfaW9uX2xuYV9jbCRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2lvbl9sbmFfY2wkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc19pb25fbG5hX2NsIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9pb25fbG5hX2NsKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2lvbl9sbmFfY2wsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpoZWFkKGNvZWZmaWNpZW50cyhlZml0X2RlcW1zX2lvbl9sbmFfY2wpKQoKZGVxbXNfcmVzdWx0c19pb25fbG5hX2NsIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2lvbl9sbmFfY2wsIGNvZWZfY29sPTMpCmBgYAoKCmBgYHtyfQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCRQLlZhbHVlKQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCRzY2EuUC5WYWx1ZSkKCmBgYAoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19pb25fbG5hX2NsJHNjYS5hZGoucHZhPDAuMSkKCmRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCAlPiUgZmlsdGVyKGxvZ0ZDPjApICU+JSBoZWFkKCkKCmRlcW1zX3Jlc3VsdHNfdmVoICU+JQogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHNjYS5QLlZhbHVlKSwgY29sb3VyID0gc2NhLmFkai5wdmFsIDwgMC4xKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfY2FtcHJvdChib3JkZXI9RkFMU0UsIGJhc2Vfc2l6ZT0xNSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygnZ3JleScsIGdldF9jYXRfcGFsZXR0ZSgyKVsyXSksIG5hbWUgPSAnSU9OIHZzIExOQSBTaWcuJykgKwogIGxhYnMoeCA9ICdJb25DTCB2cyBMTkFDTCAoTG9nMiknLCB5ID0gJy1sb2cxMChwLXZhbHVlKScpCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1MTkEgdiBWRUgtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmBgYHtyfQoKbGZxX3Byb3RlaW5fdGlkeV9MTkFfVkVIIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBmaWx0ZXIoZ3JlcGwoJ15DTF8oTE5BfFZFSClfXFxkJywgc2FtcGxlKSkgJT4lCiAgc2VwYXJhdGUoc2FtcGxlLCBpbnRvPWMoTkEsICdUcmVhdG1lbnQnLCBOQSksIHJlbW92ZSA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKFRyZWF0bWVudCkpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19sbmFfdmVoX2NsIDwtIGxmcV9wcm90ZWluX3RpZHlfTE5BX1ZFSCAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAoKbGlicmFyeSh0aWJibGUpCgpwaGVub19kYXRhMjwtIHJvd25hbWVzX3RvX2NvbHVtbihwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfbG5hX3ZlaF9jbCldKSwgdmFyID0gInNhbXBsZSIpCnBoZW5vX2RhdGEyIDwtIHBoZW5vX2RhdGEyICU+JSBzZXBhcmF0ZShzYW1wbGUsIGludG89YyhOQSwgJ1RyZWF0bWVudCcsIE5BKSwgcmVtb3ZlID0gRkFMU0UpIAoKdHJlYXRtZW50MiA8LSBwaGVub19kYXRhMiRUcmVhdG1lbnQKdHJlYXRtZW50MiA8LSBmYWN0b3IodHJlYXRtZW50MiwgbGV2ZWxzID0gYygnTE5BJywgJ1ZFSCcpKQpyZXBsaWNhdGUgPC0gcGhlbm9fZGF0YTIkUmVwbGljYXRlCgpsaW1tYV9kZXNpZ25fbG5hX3ZlaF9jbCA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+cmVwbGljYXRlK3RyZWF0bWVudDIpKQoKbGltbWFfZml0X2xuYV92ZWhfY2wgPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfbG5hX3ZlaF9jbCwgbGltbWFfZGVzaWduX2xuYV92ZWhfY2wpCmxpbW1hX2ZpdF9sbmFfdmVoX2NsIDwtIGVCYXllcyhsaW1tYV9maXRfbG5hX3ZlaF9jbCwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2xuYV92ZWhfY2wpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19sbmFfdmVoX2NsIDwtIGZpbHRlcmVkX2V4cHJzX2xuYV92ZWhfY2wgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9sbmFfdmVoX2NsIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfbG5hX3ZlaF9jbCwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfbG5hX3ZlaF9jbCRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2xuYV92ZWhfY2wkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc19sbmFfdmVoX2NsIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9sbmFfdmVoX2NsKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2xuYV92ZWhfY2wsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpoZWFkKGNvZWZmaWNpZW50cyhlZml0X2RlcW1zX2xuYV92ZWhfY2wpKQoKZGVxbXNfcmVzdWx0c19sbmFfdmVoX2NsIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2xuYV92ZWhfY2wsIGNvZWZfY29sPTMpCgpgYGAKCgpgYGB7cn0KaGlzdChkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wkUC5WYWx1ZSkKaGlzdChkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wkc2NhLlAuVmFsdWUpCgpgYGAKCgpgYGB7cn0KCnRhYmxlKGRlcW1zX3Jlc3VsdHNfbG5hX3ZlaF9jbCRzY2EuYWRqLnB2YTwwLjEpCgpkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wgJT4lIGZpbHRlcihsb2dGQz4wKSAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAoc2NhLlAuVmFsdWUpLCBjb2xvdXIgPSBzY2EuYWRqLnB2YWwgPCAwLjEpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9jYW1wcm90KGJvcmRlcj1GQUxTRSwgYmFzZV9zaXplPTE1KSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCdncmV5JywgZ2V0X2NhdF9wYWxldHRlKDIpWzJdKSwgbmFtZSA9ICdMTkEgdnMgVkVIIFNpZy4nKSArCiAgbGFicyh4ID0gJ0xOQUNMIHZzIFZFSENMIChMb2cyKScsIHkgPSAnLWxvZzEwKHAtdmFsdWUpJykKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLUlPTl9DTHYgVkVIX0NMLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkxldCdzIHZpc3VhbGlzZSB0aGUgYWJ1bmRhbmNlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlIHNhbXBsZXMgaW4gcXVlc3Rpb24uCmBgYHtyfQpwbG90X3F1YW50KGxmcV9wcm90ZWluWyxncmVwbCgnXkNMXyhJT058VkVIKV9cXGQnLCBjb2xuYW1lcyhsZnFfcHJvdGVpbikpXSwgbWV0aG9kPSdkZW5zaXR5JykgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPXJlcChnZXRfY2F0X3BhbGV0dGUoMiksIGVhY2g9MykpCnBsb3RfcXVhbnQobGZxX3Byb3RlaW5bLGdyZXBsKCdeQ0xfKElPTnxWRUgpX1xcZCcsIGNvbG5hbWVzKGxmcV9wcm90ZWluKSldLCBtZXRob2Q9J2JveCcpCmBgYAoKCgpgYGB7cn0KCmxmcV9wcm90ZWluX3RpZHlfSU9OX1ZFSCA8LSBsZnFfcHJvdGVpbiAlPiUKICAjIG1ha2UgdGhlIE1TblNldCBpbnRvICd0aWR5JyBmb3JtYXQgZm9yIGZ1cnRoZXIgdGVzdGluZwogIGJpb2Jyb29tOjp0aWR5Lk1TblNldChhZGRQaGVubz1UUlVFKSAlPiUgIyBhZGRQaGVubz1UUlVFIGFkZHMgdGhlIHBoZW5vdHlwZSBjb2x1bW5zCiAgZmlsdGVyKGdyZXBsKCdeQ0xfKElPTnxWRUgpX1xcZCcsIHNhbXBsZSkpICU+JQogIHNlcGFyYXRlKHNhbXBsZSwgaW50bz1jKE5BLCAnVHJlYXRtZW50JywgTkEpLCByZW1vdmUgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZSh2YWx1ZSkpICU+JQogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihuKCk+PTIpICU+JSAKICBncm91cF9ieShwcm90ZWluKSAlPiUKICBmaWx0ZXIobGVuZ3RoKHVuaXF1ZShUcmVhdG1lbnQpKT09MikgJT4lIyBuKCkgaXMgdGhlIGxlbmd0aCBvZiB0aGUgZ3JvdXAKICB1bmdyb3VwKCkKCiAgCiMgVGhpcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIGl0IGFsd2F5cyBiZSB0aGUgc2FtZSBzdGVwcwojIG1ha2VfZXhwcl93aWRlIDwtIGZ1bmN0aW9uKHRpZHlfZXhwcikge30KZmlsdGVyZWRfZXhwcnNfaW9uX3ZlaF9jbCA8LSBsZnFfcHJvdGVpbl90aWR5X0lPTl9WRUggJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1zYW1wbGUsIHZhbHVlc19mcm9tPXZhbHVlLCBpZF9jb2xzPXByb3RlaW4pICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCdwcm90ZWluJykgJT4lCiAgYXMubWF0cml4KCkKCiMgU2luY2UgdGhlIGNvbHVtbiBuYW1lcyBmb3Igb3VyIGZpbHRlcmVkIGV4cHJzIG1hdHJpeCBpcyBpbiB0aGUgc2FtZSBmb3JtYXQgYXMgbGZxX3Byb3RlaW4KIyB3ZSBjYW4gc3RpbGwgdXNlIHRoZSBwaGVub0RhdGEgZm9yIGxmcV9wcm90ZWluIHRvIGRlZmluZSB0aGUgY29uZGl0aW9uIGFuZAojIHJlcGxpY2F0ZSB2ZWN0b3JzLCBzbyBsb25nIGFzIHdlIHJlLW9yZGVyIGxmcV9wcm90ZWluIHVzaW5nIHRoZSBleHBycyBtYXRyaXgKIyBjb2x1bW4gbmFtZXMgZmlyc3QKCmxpYnJhcnkodGliYmxlKQoKcGhlbm9fZGF0YTI8LSByb3duYW1lc190b19jb2x1bW4ocERhdGEobGZxX3Byb3RlaW5bLGNvbG5hbWVzKGZpbHRlcmVkX2V4cHJzX2lvbl92ZWhfY2wpXSksIHZhciA9ICJzYW1wbGUiKQpwaGVub19kYXRhMiA8LSBwaGVub19kYXRhMiAlPiUgc2VwYXJhdGUoc2FtcGxlLCBpbnRvPWMoTkEsICdUcmVhdG1lbnQnLCBOQSksIHJlbW92ZSA9IEZBTFNFKSAKCnRyZWF0bWVudDIgPC0gcGhlbm9fZGF0YTIkVHJlYXRtZW50CnRyZWF0bWVudDIgPC0gZmFjdG9yKHRyZWF0bWVudDIsIGxldmVscyA9IGMoJ0lPTicsICdWRUgnKSkKcmVwbGljYXRlIDwtIHBoZW5vX2RhdGEyJFJlcGxpY2F0ZQoKbGltbWFfZGVzaWduX2lvbl92ZWhfY2wgPC0gbW9kZWwubWF0cml4KGZvcm11bGEofnJlcGxpY2F0ZSt0cmVhdG1lbnQyKSkKCmxpbW1hX2ZpdF9pb25fdmVoX2NsIDwtIGxtRml0KGZpbHRlcmVkX2V4cHJzX2lvbl92ZWhfY2wsIGxpbW1hX2Rlc2lnbl9pb25fdmVoX2NsKQpsaW1tYV9maXRfaW9uX3ZlaF9jbCA8LSBlQmF5ZXMobGltbWFfZml0X2lvbl92ZWhfY2wsIHRyZW5kPVRSVUUpCgpsaW1tYTo6cGxvdFNBKGxpbW1hX2ZpdF9pb25fdmVoX2NsKQoKIyBUaGUgbmV4dCB0d28gc3RlcHMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSB0aGV5IHdpbGwgYWx3YXlzIGJlIHRoZSBzYW1lCiMgZ2V0X21pbl9wZXB0aWRlcyA8LSBmdW5jdGlvbihmaWx0ZXJlZF93aWRlX2V4cHIpIHt9CmZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfaW9uX3ZlaF9jbCA8LSBmaWx0ZXJlZF9leHByc19pb25fdmVoX2NsICU+JQogIGRhdGEuZnJhbWUoKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbignTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPS1NYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zLCB2YWx1ZXNfdG89J2FidW5kYW5jZScsIG5hbWVzX3RvPSdzYW1wbGUnKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKGFidW5kYW5jZSkpICMgV2Ugb25seSB3YW50IHRvIGNvbnNpZGVyIHNhbXBsZXMgd2l0aCBhIHJhdGlvIHF1YW50aWZpZWQKCm1pbl9wZXBfY291bnRfaW9uX3ZlaF9jbCA8LSBjYW1wcm90Ujo6Y291bnRfZmVhdHVyZXNfcGVyX3Byb3RlaW4ocGVwKSAlPiUKICBtZXJnZShmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2lvbl92ZWhfY2wsIGJ5PWMoJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnLCAnc2FtcGxlJykpICU+JQogIAogIGdyb3VwX2J5KE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMpICU+JQogIHN1bW1hcmlzZShtaW5fcGVwX2NvdW50ID0gbWluKG4pKQojIyMKCgojIGFkZCB0aGUgbWluIHBlcHRpZGUgY291bnQKbGltbWFfZml0X2lvbl92ZWhfY2wkY291bnQgPC0gbWluX3BlcF9jb3VudF9pb25fdmVoX2NsJG1pbl9wZXBfY291bnQKCmVmaXRfZGVxbXNfaW9uX3ZlaF9jbCA8LSBzdXBwcmVzc1dhcm5pbmdzKHNwZWN0cmFDb3VudGVCYXllcyhsaW1tYV9maXRfaW9uX3ZlaF9jbCkpCgpWYXJpYW5jZUJveHBsb3QoZWZpdF9kZXFtc19pb25fdmVoX2NsLCBuID0gMzAsIHhsYWIgPSAiUGVwdGlkZXMiKQoKaGVhZChjb2VmZmljaWVudHMoZWZpdF9kZXFtc19pb25fdmVoX2NsKSkKCmRlcW1zX3Jlc3VsdHNfaW9uX3ZlaF9jbCA8LSBvdXRwdXRSZXN1bHQoZWZpdF9kZXFtc19pb25fdmVoX2NsLCBjb2VmX2NvbD0zKQpgYGAKCgpgYGB7cn0KaGlzdChkZXFtc19yZXN1bHRzX2lvbl92ZWhfY2wkUC5WYWx1ZSkKaGlzdChkZXFtc19yZXN1bHRzX2lvbl92ZWhfY2wkc2NhLlAuVmFsdWUpCgpgYGAKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19pb25fdmVoX2NsJHNjYS5hZGoucHZhPDAuMSkKCmRlcW1zX3Jlc3VsdHNfaW9uX3ZlaF9jbCAlPiUgZmlsdGVyKGxvZ0ZDPjApICU+JSBoZWFkKCkKCmRlcW1zX3Jlc3VsdHNfaW9uX3ZlaF9jbCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChzY2EuUC5WYWx1ZSksIGNvbG91ciA9IHNjYS5hZGoucHZhbCA8IDAuMSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2NhbXByb3QoYm9yZGVyPUZBTFNFLCBiYXNlX3NpemU9MTUpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZXknLCBnZXRfY2F0X3BhbGV0dGUoMilbMl0pLCBuYW1lID0gJ0lPTiB2cyBWRUggU2lnLicpICsKICBsYWJzKHggPSAnSU9OQ0wgdnMgVkVIQ0wgKExvZzIpJywgeSA9ICctbG9nMTAocC12YWx1ZSknKQpgYGA=